Chapter 14
MFC ActiveX Controls

by Gene Olafsen

In This Chapter

  Development Strategy 521
  Control Development 523
  Two Faces of a Control 526
  Subclassing a Control 527
  Component Categories 528
  Methods, Properties, and Events 530
  Property Pages 535
  Component Registration 538
  COM Object Subkeys 539
  Building an MFC Control 540
  Interface Definition 543
  A Quick ATL Port 553

One of the most visible incarnations of COM is ActiveX controls. Visual C++ 6.0 generally refers to ActiveX controls as simply controls. The control component group encompasses a wide range of component offerings from familiar image buttons to network communication libraries.

Controls built with COM are, to a certain degree, descendents of an earlier initiative by Microsoft to plug a third-party interface into a language. The language was Visual Basic and the plug-in standard was the VBX. It didn’t take long for developers to embrace VBX’s, and soon there were literally hundreds on the market, which enabled developers to implement everything from modem communication protocols to animated picture buttons. The problem with VBX’s was that as Visual Basic grew in sophistication, the VBX specification left little room for the scope Microsoft had in mind. Also, VBX’s were firmly grounded in the Visual Basic language. It would be great if a programming-language-neutral third-party interface could be defined. Here comes COM to the rescue.

A plug-in or control based on the original COM control specification required implementing a fairly hefty number of interfaces. These interfaces included those that implement property sheets, display a user interface, and manage container-control communication. For most controls, this specification was overkill. The tools and documentation that were available to aid in the development in such “compliant” controls were not up to the job. The resulting controls occupied a sizeable memory footprint, and activation was lethargic. To combat these problems, Microsoft introduced incremental improvements, including a number of control-container extensions that sped up activation and reduced UI requirements.

As the browser wars started to heat up, and Java and the JavaBean specification (a Java-based component architecture) started to take shape, Microsoft’s browser became a control container. A sticking issue was the fact that the controls were still pretty big, and took a relatively long time to download to the browser—remember that most modems were cranking along at 2400 to 9600 baud. The control specification was changed one last time, where it remains today.

The control requirements are as follows:

  Implement IUnknown
  Support self-registration

The specification of a control is thus so broad that it encompasses just about any COM server. This is exactly the point—in this manner, Microsoft defines a requirement set that has minimal overhead and allows the developer to produce components that are tailored for an exact purpose.

Development Strategy

Both MFC and ATL support control development. In fact, both make it relatively painless to develop controls through the support of various wizards and powerful helper classes or templates. There are certain advantages to creating your control using either framework, and it is important to understand the major issues with each before beginning development.

MFC

The Microsoft Foundation Classes have been around for a long time, and you are certainly familiar with the object hierarchy, even if you haven’t explored control development. Because there can be a lot more to control development than simply generating the skeleton code and pointing to the control’s device context, your familiarity with the MFC collection classes, runtime identification, and creation and serialization mechanisms can be a big advantage in writing the code for the control.

There are, however, some disadvantages to creating a control with MFC. One of the easiest ways to determine whether it is appropriate for you to build a control using MFC is to identify the context in which you will use the control. If you are building the control as part of the process of componentizing an installable application, or you intend the control for use as part of a toolkit for another developer’s application, basing the control on MFC is appropriate. If, however, you expect control deployment to occur on an HTML page, you might have to consider several points.

Controls built using MFC require the presence of the appropriate MFC DLLs for the control to execute. This might not be an issue for controls in use as part of an installable application, because the libraries can be included on the distribution media, but it can be an issue with use in a browser. The end user can navigate to a page on the Internet with an ActiveX control that is built with MFC, and the appropriate libraries might not be present on the user’s system. In addition, controls built using MFC can become quite large, and downloading the control can be frustrating to the end user. The DLLs weigh in at over one megabyte in size, so download is tedious at best. This is not to say that you cannot build a lightweight control using MFC, but if size is an issue, there is an alternative: the Active Template Library (ATL).

ATL

The Active Template Library offers you a lightweight alternative to controls built using MFC. The term lightweight doesn’t necessarily refer to the fact that controls built with ATL are less powerful than their MFC counterparts; rather, it refers to the fact that this new framework carries around less baggage than its older sibling.

ATL utilizes templates (no surprise there) in the creation of COM components. A template, at the most basic level of understanding, offers you a way to specify a generic representation of a class and a template and then create parameterized instances of the class. The ATL framework offers a large number of templates for many of the standard COM interfaces. In use, templates differ from a class hierarchy in that there is no additional complexity as the result of inheritance. The resulting class doesn’t depend on overriding virtual functions, thus there is no overhead in terms of execution penalty or object size because the functionality is not dependent on vtables. In summary, the footprint for ATL-based components is smaller than for the equivalent MFC-based control.



MFC and ATL

The previous section might make you believe that there is an impenetrable barrier between framework approaches. You select one approach, stick with it, and ignore the other option. In fact, this relationship isn’t as antagonistic as you might think. It is quite possible to use MFC classes within an ATL project, as well as create ATL-based COM objects in an MFC application.

The ATL COM AppWizard (see Figure 14.1) allows you to specify that you want MFC support during project creation.


Figure 14.1  ATL COM AppWizard, step 1.

The Support MFC option is available to DLL projects only, and it links the MFC library to your code. This allows you to access any MFC classes or functions. Selecting this option will not necessarily add a significant amount of code to your base project, but it will establish a dependency with the MFC library. This approach contains your project in an MFC application object, which initializes and frees an ATL module. In essence, it is the same as creating an MFC DLL project and inserting ATL components.

The class declaration for the project identifies an object that derives from CWinApp:

class CATL_COMApp : public CWinApp
{
    public:
    virtual BOOL InitInstance();
    virtual int ExitInstance();
};

The implementation in InitInstance associates an application instance handle with a managed list of ATL objects:

CATL_COMApp theApp;

BOOL CATL_COMApp::InitInstance()
{
    _Module.Init(ObjectMap, m_hInstance, &LIBID_ATL_COMLib);
    return CWinApp::InitInstance();
}

int CATL_COMApp::ExitInstance()
{
    _Module.Term();
    return CWinApp::ExitInstance();
}

Supporting ATL in an MFC project is possible for both EXE- and DLL-based applications. The Insert menu offers access to the New ATL Object dialog (see Figure 14.2).


Figure 14.2  The ATL Object Wizard dialog.

This dialog allows you to insert all the ATL components that are available to an ATL-based project. There is one issue with this approach, however. Visual C++ supports insertion only as it applies to simple COM objects. Adding more complex objects, which includes ActiveX controls, can result in unexpected behavior.

Control Development

Part of selecting a control’s foundation may include considering the extent of support that the wizard provides in generating skeleton code. Each of the control implementations makes it simpler to perform different objectives. Table 14.1 lists these functions.

Table 14.1 Control Implementations and Their Functions

Category Feature MFC ATL
(Full Control)

General Wizard Support for multiple controls X X
Options in a project
Runtime licensing X
Comments in code X X
Help files X
Threading: single/apartment X
Interfaces Dual/custom X
Aggregation: Yes/No/Only X
ISupportErrorInfo X
Connection Points X X
Free-threaded marshaler X
Control Subclassing button X X
combobox X X
edit X X
listbox X X
richedit X
msctls_hotkey32 X
msctls_progress32 X
msctls_statusbar32 X
msctls_trackbar32 X
msctls_updown32 X
scrollbar X X
static X X
SysAnimate32 X X
SysHeader32 X X
SysListView32 X X
SysTabControl32 X X
SysTreeView32 X X
Features Active when visible X
Invisible at runtime X X
Available in Insert Object dialog X X
Has an About box X
Acts as a simple frame control X
Acts like a label X
Acts like a button X
Opaque background X
Solid background X
Enhanced Features Windowless activation X
Unclipped device context X
Flicker-free activation X
Mouse pointer notifications when inactive X
Optimized drawing code X
Load properties asynchronously X
Normalize DC X
Windowed only X
Stock property selection X



This comparison is of the ATL full control to an MFC Active ControlWizard component. ATL supports the following control types:

  Full control
  HTML control
  Composite control
  Lite control
  Lite HTML control
  Lite composite control

Controls other than the full control offer subsets of the functionality that the full control wizard implements (see Figure 14.3).


Figure 14.3  Numerous ATL control objects.

There are two primary control categories, those that are specified as lite and their regular implementations. The lite controls support only those interfaces needed by Internet Explorer, including support for a user interface. The HTML control includes a DHTML resource and displays an HTML Web page as its user interface. The composite control can host multiple controls within itself.

Two Faces of a Control

Suppose you are developing a control that provides a graphic representation of a person’s vital signs—pulse, respiration, and so on. Such a control is certainly a candidate for implementing user interface extensions, such as screen invalidation, focus management, and keyboard traversal. However, if you are building a control whose purpose is to control frequency hopping for a spread spectrum radio modem, you might not need a user interface at runtime, but what about at design time? Controls can implement separate user interfaces for runtime and design time.

Runtime

The runtime UI is the one that you commonly associate with a control (see Figure 14.4). If the control provides image button functionality, you think of the arrangement of buttons that are selected either by the click of the mouse or by keyboard hot keys/accelerators. You can also envision the button’s color changing as a mouse passes over or the image changing as the button is clicked.


Figure 14.4  The Date time picker control at runtime.

Design Time

The design-time UI is usually displayed only to the developer. Consider the frequency hopping control discussed earlier. The control may not have a graphical runtime component because it is not preferable for the user to know anything about the control’s operation. However, the designer might want to configure or tailor the control for operation in the product under development (see Figure 14.5). A design-time user interface can offer the developer a range of frequencies to hop between, perhaps in a multiselect list box. A combo box can offer a choice of algorithms that are applied to filter background noise.


Figure 14.5  Date time picker control properties.

Subclassing a Control

Creating a control can be a tedious process; that is why both ATL and MFC provide the ability to subclass an existing control.

MFC

Selection of the control to subclass is made from the wizard during project creation. The class name of the control to subclass is wired into the COleControl class during the window precreate function.

/////////////////////////////////////////////////////////////////////
// CSublistboxCtrl::PreCreateWindow - Modify parameters
//                                    for CreateWindowEx

BOOL CSublistboxCtrl::PreCreateWindow(CREATESTRUCT& cs)
{
    cs.lpszClass = _T(“LISTBOX”);
    return COleControl::PreCreateWindow(cs);
}

The wizard automatically overrides the IsSubClassedControl function to return TRUE for a control that subclasses a well-known control.

/////////////////////////////////////////////////////////////////////
// CSublistboxCtrl::IsSubclassedControl - This is a subclassed control

BOOL CSublistboxCtrl::IsSubclassedControl()
{
    return TRUE;
}

Access to control messages is available in the handler that the wizard provides for you.

/////////////////////////////////////////////////////////////////////
// CSublistboxCtrl::OnOcmCommand - Handle command messages

LRESULT CSublistboxCtrl::OnOcmCommand(WPARAM wParam, LPARAM lParam)
{
#ifdef _WIN32
    WORD wNotifyCode = HIWORD(wParam);
#else
    WORD wNotifyCode = HIWORD(lParam);
#endif

    // TODO: Switch on wNotifyCode here.

    return 0;
}

ATL

The ATL approach provides the framework with the class name of the control that you subclass in the constructor:

Csublistbox()  : m_ctlListBox(_T(“ListBox”), this, 1)
{
    m_bWindowOnly = TRUE;
}

You add event handlers to the control by right-clicking the control’s class in the Class View tab of the project window and selecting the Add Windows Message Handler menu.

Component Categories

The first COM-based controls implemented a large number of interfaces. Through various calls to these interfaces, a container application could go through a discovery process to identify various characteristics of a control and display it accordingly. The most recent control specification literally identifies any COM component as a control—a different means of identifying and categorizing controls was needed. The concept of component categories offers an extensible solution to classifying control characteristics.

As with any system that provides an extensible mechanism for creating new entries in a namespace that is not centrally managed, there is the possibility, if not certainty, that a collision will occur. Allowing developers to use character strings to establish new component categories can result in one or more programmers using the same value to mean two different things. Fortunately, this problem is inherent to COM and has already been solved.

Remember that every COM server and every interface is uniquely identified by a Globally Unique IDentifier (GUID). The term GUID also has a number of manifestations that are specific to the object or element that it describes. A CLSID (CLass IDentifier) is a GUID that describes a class, whereas an IID (Interface IDentifier) describes an interface. Component categories also employ GUID technology to avoid collision as well. A CATID or category identifier is a GUID that is associated with control category identification.

COM defines a number of interfaces that make identifying component categories an easier task.

ICatRegister

The ICatRegister interface offers functions for registration and unregistration of component category information.

ICatInformation

The ICatInformation interface offers functions that obtain information about categories that a class implements. Additionally, this interface can return information about categories that a machine registers.

Registered component categories appear under the HKEY_CLASSES_ROOT\Component Categories key. The categories on a system can be reviewed either by firing up RegEdit or RegEdt32 and viewing the path given in the last sentence or by looking at Object Classes\Grouped by Component Category in the OLEVIEW utility. In this case the Bitmap Transition category contains two entries: Alpha Transition and Reveal Transition (see Figure 14.6).


Figure 14.6  Using Object Viewer to identify component categories.



Methods, Properties, and Events

The control guidelines specify that a control expose its methods, properties, and events through an IDispatch-derived or dual interface.

Properties

Properties represent a control’s internal state. A common naming convention for property methods is prepending the words get or set to the beginning of the attribute you are defining. In fact, the concept of object properties is not unique to COM controls. Java offers a component architecture named JavaBeans. Part of this specification is a design pattern (naming convention) that requires property methods to begin with either the word get or set. The Java container for these components identifies properties by evaluating the actual name of the method. COM identifies property methods using an IDL construct named property.

Property Types

The flexibility that COM offers should never cease to amaze you. It would be simple if COM offered a single type of property, but no, that would be much too restricting. So COM provides three kinds of properties: Custom, Stock, and Ambient. As you will see, it only makes sense that all of these property types exist.

Custom Properties

The term “custom” might seem at first to be the most involved type of property to add to a control. In fact, custom properties are probably the easiest property type to understand and the easiest to add and implement. Custom properties are simply a pair of get/set methods that are added to a control. These methods can be as simple as setting a Boolean state (set) and returning a Boolean value (get), or they can expose and manage an array of objects.

A developer can define properties as read-only or write-only. Read-only properties can only be set. This is accomplished by not exposing a get method. Write-only properties can only be set—their get method is not defined.

As with methods and events, you can create custom properties in one of two ways: either edit the component’s IDL or use a wizard to do the dirty work. There are helpful wizards for creating your own control properties for both ATL and MFC controls.

Stock Properties

The next property type to discuss is stock properties. A stock property is a property whose purpose is defined by OLE/COM. Stock properties can be exposed by a control and identify specific characteristics that Microsoft deems are important for a container application to be interested in. Such characteristics generally concern themselves with the control’s visual representation. Unlike custom properties, which have a positive integer DISPID, stock properties are identified by a negative integer DISPID. The following table contains each property’s DISPID constant, the constant’s value, and a description of the property:

Stock Property Dispatch Entry Macro

Appearance DISP_STOCKPROP_APPEARANCE
BackColor DISP_STOCKPROP_BACKCOLOR
BorderStyle DISP_STOCKPROP_BORDERSTYLE
Caption DISP_STOCKPROP_CAPTION
Enabled DISP_STOCKPROP_ENABLED
Font DISP_STOCKPROP_FONT
ForeColor DISP_STOCKPROP_FORECOLOR
hWnd DISP_STOCKPROP_HWND
Text DISP_STOCKPROP_TEXT

The datatypes for each stock property are specific to the value or values that the property is identified with. For instance, the MousePointer property exposes methods that get and set an HCURSOR value, while the FillColor property can be translated to a COLORREF.

Ambient Properties

The final type of property to explore is ambient properties. Ambient properties are a way for the container to “give back” to the control a meager offering in appreciation of all the properties the control has exposed. More to the point, ambient properties are read-only values that represent characteristics of a control’s container. As with stock properties, ambient properties generally reflect visual states of the container, including foreground/background colors and text alignment information. The following table summarizes the ambient property names and constants:

Ambient Property DispID Constant

LocaleID -705
UserMode -709
UIDead -710
ShowGrabHandles -711
ShowHatching -712
DisplayAsDefault -713



Accessing Ambient Properties

Ambient properties are exposed by the container through its default IDispatch interface. Controls are required to implement the IOleObject interface. This interface is the principal means by which a control provides functionality to, and communicates with, its container. The default IDispatch interface, by which ambient properties are accessed, is passed to the control with a call by the container on the client’s IOleObject::SetClientSite method.

ATL

The ambient properties exposed by a container can be accessed through the GetAmbientX methods, where X is the property name (as defined in the preceding table), provided by the CComControl class.

MFC

Accessing ambient properties in an MFC-implemented control is painless. The COleControl base class provides two ways to acquire ambient property information. The easiest way is to call any one of the number of functions of the form Ambient<property-name>. Such functions include AmbientBackColor, AmbientUserMode, and so on.

A second way to acquire ambient property information is to use a more generic call named GetAmbientProperty. This call is useful when responding to ambient property changes because of the returned dispid value.

Responding to Change

A control usually queries its container for ambient property values during initialization. Many of these values are then used to configure the control so that its appearance reflects the look and feel of the container. An important aspect to consider when dealing with ambient properties is that they can change at any time. Although chances are slim that the LocaleID or MessageReflect properties will modify during a control’s lifetime, it is not a stretch to say that container colors or the control’s activation state will remain the same.

MFC- and ATL-based controls are implementations of the IOleControl interface. This interface provides a method that amounts to a callback, which returns the DISPID of an ambient property when the container changes the value.

HRESULT OnAmbientPropertyChange(DISPID dispid);

Methods

The methods that your control exposes are little more than a variation on the OLE automation method theme. Because the methods that your control exposes exist in a dispinterface, argument datatypes must be compatible with the VARIANT datatype.

Visual C++ provides wizards to help construct methods for the controls you build with either ATL or MFC. Chapter 12 contains further details on the creation of methods and the available helper dialogs.

Events

Events are a further variation on the automation method theme. As a Windows programmer, you are undoubtedly familiar with the events that window messages represent. You program in a manner that is different from procedural programs. As a general rule, instead of guiding a user through your program, your program responds to actions taken by the user, hence the term event-driven programming. The concept of events in OLE controls extends this notion that allows the control to inform the container that something happened. When the control wants to notify the container of a change, it is said to fire an event.

Adding events to a control is not much different from adding standard methods. The difference is that you must add the event definitions to an outgoing interface. Methods and properties exist in incoming interfaces.

The following ODL describes a control’s coclass with an interface for methods and properties and an outgoing (denoted by source) interface for events: _DMFCActiveXEvents.

[ uuid(D33E5F36-CC5B-11D2-8FBA-00105A5D8D6C),
  helpstring(“MFCActiveX Control”), control ]
coclass MFCActiveX
{
    [default] dispinterface _DMFCActiveX;
    [default, source] dispinterface _DMFCActiveXEvents;
};

Although the specifics differ between MFC and ATL with regard to implementation of the event methods, wizards generally perform all the work for you in creating the event functions that you call in your control code to issue notification to the container. For MFC, an event map manages event methods on your outgoing interface.

// Event maps
//{{AFX_EVENT(CMFCActiveXCtrl)
void FireDataStream(BSTR Control)
    {FireEvent(eventidDataStream,EVENT_PARAM(VTS_BSTR), Control);}
void FireThermalValue(short Indicator)
    {FireEvent(eventidThermalValue,EVENT_PARAM(VTS_I2), Indicator);}
//}}AFX_EVENT
DECLARE_EVENT_MAP()

Whenever you want to notify the container of a thermal condition change, you simply call FireThermalValue with a short argument.

ATL generates a class from the IConnectionPointImpl template and defines the necessary event methods.

template <class T>
class CProxy_IATLActiveXCtlEvents : public IConnectionPointImpl<T, \
 &DIID__IATLActiveXCtlEvents, CComDynamicUnkArray>
{
    //Warning this class may be recreated by the wizard.
public:
    HRESULT Fire_ThermalValue(SHORT nIndicator)
    {
        CComVariant varResult;
        T* pT = static_cast<T*>(this);
        int nConnectionIndex;
        CComVariant* pvars = new CComVariant[1];
        int nConnections = m_vec.GetSize();

        for (nConnectionIndex = 0; nConnectionIndex < nConnections;
             nConnectionIndex++)
        {
            pT->Lock();
            CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
            pT->Unlock();
            IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
            if (pDispatch != NULL)
            {
                VariantClear(&varResult);
                pvars[0] = nIndicator;
                DISPPARAMS disp = { pvars, NULL, 1, 0 };
                pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT,
                     DISPATCH_METHOD, &disp, &varResult, NULL, NULL);
            }
        }
        delete[] pvars;
        return varResult.scode;
    }
};

In this case, an ATL control programmer calls Fire_ThermalValue, again with a short value, to notify the container application of a change in “temperature.”



Property Pages

The ability to view and/or modify an object’s properties can be programmed in a number of ways. Many development environments provide property inspectors that identify an object’s property methods, datatypes, and, if possible, a collection of appropriate values. Visual Basic and Visual J++ are examples of development systems that display a property editor window that displays and allows modification of a selected object’s attributes at design time.

There are times, however, when either such generic property editors do not suffice, because of property interdependencies, or the property values are a datatype that is not easily configurable without a more complex interface (see Figure 14.7). In fact, a property sheet may not be confined to design-time only availability. Property sheets may be available at runtime as well, although some functionality might be gated by disabling controls or by presenting read-only access to an object’s internal state. Other times, property sheets can offer much more dynamic runtime configuration.


Figure 14.7  A generic property editor window.

Finally, property sheets are not confined to controls. Windows shortcuts offer a perfect example of property sheets that allow a user to display and interact with property settings at runtime (see Figure 14.8).


Figure 14.8  An OLE control property sheet.

A wizard is available to develop property page objects using ATL. This wizard automates much of the grunge work that such page construction requires. If you want to construct property page objects for MFC-based controls, you must do a little more work; however, MFC is kind enough to provide a few “helper” classes.

The implementation definition for property pages exists as the IPropertyPage COM interface. This interface manages a single page within a property sheet collection. Each property page is a COM component. A property page object must create and manage the UI components that reside on the page. The collection of pages or sheets is an implementation of the IPropertyPageSite COM interface. An object that implements IPropertyPageSite is responsible for managing the pages that appear within the dialog. Property page changes are communicated back to the control through its default IDispatch interface.

Property Pages in ATL

Creating a property page object in ATL is as simple as adding another COM object to your existing project. Using the Insert, New ATL Object menu, the ATL Object Wizard allows the selection of a Property Page object from the Controls category selection.

Selecting the Next> step displays a dialog with three tabs. The first two, Names and Attributes, are identical to those found in the Simple Object wizard that has been discussed in detail in earlier chapters. The third tab, Strings, is specific to the property page object.

The Strings page offers three edit fields: Title, Doc String, and Helpfile (see Figure 14.9). It isn’t too difficult to discern the meaning and acceptable values for each of these fields. An important thing to remember is that inserting this object into your project inserts a single property page. If you want to have more than one property page associated with your project, simply add another Property Page object.


Figure 14.9  Configuring an ATL property page.

Property Pages in MFC

If you are developing a new control in MFC using ControlWizard, a single property page is created by the wizard’s code generation facilities. If you are adding additional pages to the control, you must use ClassWizard. In either case, MFC-based controls rely on COlePropertyPage as the base class for your own property pages.

Adding property pages to an MFC control is not difficult—if you know where to look. Switch your project workspace window to the ResourceView tab. Right-click any of the resource objects and select the Insert menu option to display the Insert Resource dialog (see Figure 14.10). You will not find a property page resource type to insert, but the Dialog resource object can expand the tree. Clicking this object reveals a number of resource templates.


Figure 14.10  Dialog resource templates for OLE property sheets.

The IDD_OLE_PROPPAGE_LARGE and IDD_OLE_PROPPAGE_SMALL templates should interest you. These templates are the proper size and exhibit the correct style settings for OLE property sheets.

To add the necessary code behind a property sheet, insert a new class that derives from COlePropertyPage (see Figure 14.11).


Figure 14.11  Creating a class that derives from COlePropertyPage.

The New Class dialog doesn’t perform all the necessary work for you, though. You need to manually add the PROPPAGEID macro entry to the MFC property page map construct. Make sure that the macro entry you provide includes the proper GUID for the page. In addition, you must increment the property page count argument in the BEGIN_PROPPAGEIDS macro. The application wizard kindly generates a reminder in the comment block that precedes the map:

// TODO: Add more property pages as needed.
// Remember to increase the count!
BEGIN_PROPPAGEIDS(CMFCActiveXCtrl, 1)
    PROPPAGEID(CMFCActiveXPropPage::guid)
END_PROPPAGEIDS(CMFCActiveXCtrl)

Component Registration

This section provides an overview of the registration process for MFC-based and ATL-based servers and their contained objects: classes and type libraries. Registration is the process of making information about a component, such as its location and function, known to the operating system and other installed applications. The Windows Registry is a database that stores COM server, object, and type library information.

Registering a control adds the following information to the registration database:

  The text name of the control
  The class name of the control
  An indicator stating that the control conforms to the MFC ActiveX control protocols
  The path of the control’s executable
  The path and resource ID of the control palette bitmap
  An indication of whether the control is insertable
  The IDispatch IDs of the control’s properties and events interfaces

Registration Scripts

Component registration (and unregistration) is performed by registration scripts. Registration scripts generally exist in a file with an .RGS extension. ATL supports registration scripts with a COM component, deriving from IDispatch and whose name is IComponentRegistrar.

Registration scripts can either be stored inside the COM server file they register or appear in standalone files. A registration script is stored in a server as part of the component’s resource file.



Registration and Controls

The second requirement for a COM object to identify itself as a legal control is self-registration. A number of keys are important in identifying a component as a control.

Because the definition of a control is broad enough to encompass any COM object, the location of control registration in the registry is no different than any other component. Controls are located under \HKEY_CLASSES_ROOT\CLSID\<clsid>. The subkeys described in the following sections, however, are commonly present as part of a control’s definition.

COM Object Subkeys

Unless otherwise defined, it is assumed that the default value for each key is undefined (“”):

  \Programmable—The presence of the Programmable key indicates that the object supports automation.
  \Insertable—This key identifies the object as one that should appear in the Insert Object dialog box’s list of possible selections.
  \Control—Indicates that the object that possesses this key is to be included in a list of registered controls for applications that display a list of controls.
  \ToolboxBitmap32—This key’s default value specifies the name of the file and the index of the resource (icon) to display for the component. This icon is a 16×16 pixel resource and is generally displayed in the tool palette of an application that exploits the control’s design-time interface.
  \MiscStatus—This key aids in the construction and presentation of the object, including aspect ratio values.
  \Verb—This key is instrumental in registering OLE-compatible verbs and associated menu flags. Common verbs include Open, Hide, and Edit.
  \TypeLib—This key contains the CLSID of the type library for the object.
  \InprocServer32—Use this key to register a 32-bit in-process server object. Additionally, this key identifies the server’s threading model: Single, Apartment, Free, or Both (Apartment or Free).
  \InprocHandler32—This key specifies a custom handler used by an application. Generally, this entry should be COM32.DLL.
  \LocalServer—This key specifies a path to a 16-bit server object.
  \LocalServer32—This key specifies a path to a 32-bit server object.
  \ProgID—The ProgID key associates a human-readable tag with a CLSID.
  \Implemented Categories—Identifies the category functions that this server object implements.
  \DefaultIcon—The DefaultIcon key allows you to specify the icon to display for OLE servers. Thus, this is the icon that displays when a control is minimized or an OLE document server object is inserted as an icon.
  \DataFormats—This key identifies an object’s data formats. Such data formats can be enumerated using EnumFormatEtc.
  \AuxUserType—This key provides an object’s short name, which is usually recommended to be no longer than 15 characters.
  \TreatAs—This key enables an OLE server to work in place of another server object. This key provides a way for your object to offer emulation for a different object—perhaps one you didn’t even write.
  \Version—This key identifies the version of the OLE server and should match the type library associated with the server.

Building an MFC Control

The control that is going to be built using both MFC and ATL performs the radar-mapping functions for a space probe that is scheduled to enter orbit around Mars later this year. Okay, I’m lying. The control simulates a component that performs such mapping functions. As such, it exposes a number of methods and properties and fires events, which identify the data stream and any error conditions. The control also offers a very primitive user interface. The following table identifies the methods, properties, and events for this control:

Type Name Datatype Description

Method SetAntenna Short Identifies the antenna to use for collection/ transmission.
Property m_AcquireData Boolean Turns on and off the event firing that passes the data buffer.
Property m_BufferSize Short Identifies the size of the data buffer.
Event DataStream BSTR The data buffer is sent to the container when the buffer is full.
Event ThermalValue Short Identifies thermal changes.

Creating a control using the MFC ActiveX ControlWizard results in the creation of a DLL (in-proc server) with an .OCX extension. Unlike its ATL counterpart, which uses IDL, the interface from this control resides in an .ODL file, and its definition employs the Object Definition Language. The name of this project is MFCActiveX (see Figure 14.12). Accept all the default values for the steps of the wizard.


Figure 14.12  Starting an ActiveX control using MFC ActiveX ControlWizard.

There are two classes that perform the bulk of the work for the control, COleControlModule and COleControl. The COleControlModule class performs a function similar to that of CWinApp in a standard MFC application. COleControlModule provides both the InitInstance and ExitInstance virtual functions.

/////////////////////////////////////////////////////////////////////
// CMFCActiveXApp::InitInstance - DLL initialization

BOOL CMFCActiveXApp::InitInstance()
{
    BOOL bInit = COleControlModule::InitInstance();

    if (bInit)
    {
        // TODO: Add your own module initialization code here.
    }

    return bInit;
}


/////////////////////////////////////////////////////////////////////
// CMFCActiveXApp::ExitInstance - DLL termination

int CMFCActiveXApp::ExitInstance()
{
    // TODO: Add your own module termination code here.

    return COleControlModule::ExitInstance();
}



The file that contains these definitions also contains two functions that every in-proc server must export: DllRegisterServer and DllUnregisterServer. These functions offer known entry points into the DLL for applications that perform control registration. The Regsvr32.exe is one such program that exercises a control in this manner. Your Visual C++ Tools menu contains a Register Control entry, which uses the Regsvr32 utility to register the control or controls in your current project. This utility also supports a number of command-line arguments. Among the most useful is the /u option, which unregisters a control—calling DllUnregisterServer.

/////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void)
{
    AFX_MANAGE_STATE(_afxModuleAddrThis);

    if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid))
        return ResultFromScode(SELFREG_E_TYPELIB);

    if (!COleObjectFactoryEx::UpdateRegistryAll(TRUE))
        return ResultFromScode(SELFREG_E_CLASS);

    return NOERROR;
}
/////////////////////////////////////////////////////////////////////
// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void)
{
    AFX_MANAGE_STATE(_afxModuleAddrThis);

    if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor))
        return ResultFromScode(SELFREG_E_TYPELIB);

    if (!COleObjectFactoryEx::UpdateRegistryAll(FALSE))
        return ResultFromScode(SELFREG_E_CLASS);

    return NOERROR;
}

Registration of a control usually takes place when you install a control. It is important to remember to reregister your control whenever you make changes that affect the type library. Such changes include supporting or removing support for properties and events.

The COleControl class is the base class from which you derive your control. A project can contain more than one control, each deriving from this class. In fact, the MFC ActiveX ControlWizard allows you to specify the number of controls in the project, and it allows you to identify the properties and options that you want to support on an individual control basis.

Interface Definition

Now it is time to add the methods, properties, and events to the control. The first thing to do is switch the workspace pane to display the Class View (see Figure 14.13). You will notice that there are two interfaces: _DMFCActiveX and _DMFCActiveXEvents. The first interface, _DMFCActiveX, is a standard incoming interface. Such an interface identifies methods and properties that are called from a container application. This type of interface was explored in Chapter 12, “MFC OLE Servers.” The second interface, _DMFCActiveXEvents, is an outgoing interface. An outgoing interface is one that calls the container, usually as the result of an event inside the control. A common use of an outgoing event is one in which the control mimics the behavior of a button control and a mouse click on the ActiveX control issues a click event.

Going in order from methods to properties and finally events, you will start by adding methods to the control. There is only a single method, SetAntenna, that takes a short datatype representing the antenna to use for data collection. Add the method by selecting the Add Method menu selection from the context menu that you display with a right-click on the _DMFCActiveX interface.


Figure 14.13  Classes and interfaces as seen in the Class View pane.

The method dialog is identical to the Add Method dialog that you use when creating automation servers (see Figure 14.14). In this case the method will return an SCODE and accepts a single short parameter whose name is Antenna.


Figure 14.14  The Add Method context menu.

The control also contains two properties, one to turn data acquisition on and off and another to set and retrieve the data buffer size. The data buffer is actually a variable-length BSTR, so this value is more of a high-water marker, rather than for use in allocating a predefined buffer. You create properties using a context menu to select Add Property and populate the Add Property dialog.

The first property’s definition includes a name of AcquireData of type BOOL. The second property’s name is BufferSize and its datatype is short.

The next step is to define the two control events. This time you will make modifications to the _DMFCActiveXEvents interface. Clicking the right mouse button on this interface in Class View results in a context menu that offers an Add Event menu (see Figure 14.15).


Figure 14.15  The Add Event context menu.

The Add Event dialog box offers a combo box into which you enter the External Name of the event or select from a list of predefined events. These event types include the following:

  Click
  Double-Click
  Error
  KeyDown
  KeyUp
  KeyPress
  MouseDown
  MouseMove
  MouseUp
  ReadyStateChange

Provide the name DataStream with a single argument control whose datatype is a BSTR*. The dialog will automatically generate an internal name of FireDataStream, which you can edit if necessary (see Figure 14.16).


Figure 14.16  The Add Event dialog for the DataStream event.



The next event reports thermal conditions inside the scanning unit. The name you give to this event is ThermalValue, and it defines a single short argument whose name is indicator. Upon defining all the methods, properties, and events for the control, your interface looks like Listing 14.1.

Listing 14.1 The Completed Interface


// MFCActiveX.odl : type library source for ActiveX Control project.

// This file will be processed by the
// Make Type Library (mktyplib) tool to
// produce the type library (MFCActiveX.tlb)
// that will become a resource in
// MFCActiveX.ocx.

#include <olectl.h>
#include <idispids.h>

[ uuid(D33E5F33-CC5B-11D2-8FBA-00105A5D8D6C), version(1.0),
  helpfile(“MFCActiveX.hlp”),
  helpstring(“MFCActiveX ActiveX Control module”),
  control ]
library MFCACTIVEXLib
{
    importlib(STDOLE_TLB);
    importlib(STDTYPE_TLB);

    //  Primary dispatch interface for CMFCActiveXCtrl

    [ uuid(D33E5F34-CC5B-11D2-8FBA-00105A5D8D6C),
    helpstring(“Dispatch interface for MFCActiveX Control”), hidden ]
    dispinterface _DMFCActiveX
    {
        properties:
       // NOTE - ClassWizard will maintain property information here.
            //    Use extreme caution when editing this section.
            //{{AFX_ODL_PROP(CMFCActiveXCtrl)
            [id(1)] boolean AcquireData;
            [id(2)] short BufferSize;
            //}}AFX_ODL_PROP

        methods:
         // NOTE - ClassWizard will maintain method information here.
            //    Use extreme caution when editing this section.
            //{{AFX_ODL_METHOD(CMFCActiveXCtrl)
            [id(3)] SCODE SetAntenna(short Antenna);
            //}}AFX_ODL_METHOD
            [id(DISPID_ABOUTBOX)] void AboutBox();
    };
    //  Event dispatch interface for CMFCActiveXCtrl

    [ uuid(D33E5F35-CC5B-11D2-8FBA-00105A5D8D6C),
      helpstring(“Event interface for MFCActiveX Control”) ]
    dispinterface _DMFCActiveXEvents
    {
        properties:
            //  Event interface has no properties
        methods:
          // NOTE - ClassWizard will maintain event information here.
            //    Use extreme caution when editing this section.
            //{{AFX_ODL_EVENT(CMFCActiveXCtrl)
            [id(1)] void DataStream(BSTR Control);
            [id(2)] void ThermalValue(short Indicator);
            //}}AFX_ODL_EVENT
    };
    //  Class information for CMFCActiveXCtrl

    [ uuid(D33E5F36-CC5B-11D2-8FBA-00105A5D8D6C),
      helpstring(“MFCActiveX Control”), control ]
    coclass MFCActiveX
    {
        [default] dispinterface _DMFCActiveX;
        [default, source] dispinterface _DMFCActiveXEvents;
    };


    //{{AFX_APPEND_ODL}}
    //}}AFX_APPEND_ODL}}
};

The last step is to supply code and flesh out the skeleton framework. Contriving the operations for this component is almost more difficult than pulling down menus and filling out dialog boxes. The gist of this control’s operation is that the control asynchronously notifies its container of data. The antenna that the container selects determines the interval at which the control notifies its container. The control also asynchronously notifies its container of various thermal conditions. If the control sends too much data in a short amount of time, it overheats and stops the data collection operation.

The SetAntenna method allows you to select one of three antennas, whose values are, oddly enough, one, two, and three.

SCODE CMFCActiveXCtrl::SetAntenna(short Antenna)
{
    // the antenna selection identifies the supported data rate
    m_nAntenna = Antenna;
    switch(Antenna)
    {
    case 1:
        m_nDataRate = 100;
        break;
    case 2:
        m_nDataRate = 10;
        break;
    default:
        m_nDataRate = 5;
        break;
    }

    InvalidateControl();
    SetModifiedFlag();

    return S_OK;
}

The antenna setting directly affects the data rate of the component. A setting of one yields the highest data rate, three the lowest.

The m_AcquireData Boolean property turns data collection on and off. Setting this variable to a TRUE state establishes a timer that results in data generation as a function of the current data rate selection.

void CMFCActiveXCtrl::OnAcquireDataChanged()
{
    if (m_acquireData)
    {
        SetTimer(3442, (1000/m_nDataRate)*500, NULL);
    }
    else
    {
        KillTimer(3442);
    }
    SetModifiedFlag();
    InvalidateControl();
}

The m_BufferSize property specifies the limit of data that is collected before it is returned to the container. The DataStream event returns data to your container, and the ThermalValue reports a temperature condition as a short between the values of one and three.

void CMFCActiveXCtrl::OnTimer(UINT nIDEvent)
{
    if (nIDEvent == 3442)
    {
        CString sData;

        // create data stream
        if ((m_nDataCount % 2))
            sData = “1”;
        else
            sData = “0”;
        ++m_nDataCount;

        // convert data stream to BSTR
        BSTR bstrData = sData.AllocSysString();

        // issue event
        FireDataStream(bstrData);

        // thermal values based on how much “work” is being performed
        DWORD dwCurrentTick = ::GetTickCount();
        if ((dwCurrentTick - dwLastTick) < 3000)
        {
            FireThermalValue(1);
            m_nDataCount = 501;
            m_nThermal = 3;
        }
        else if ((dwCurrentTick - dwLastTick) < 6000)
        {
            FireThermalValue(2);
            m_nThermal = 2;
        }
        else if ((dwCurrentTick - dwLastTick) < 10000)
        {
            FireThermalValue(3);
            m_nThermal = 1;
        }
        // store tick count
        dwLastTick = dwCurrentTick;

        // check maximum transmission
        if (m_nDataCount > 500)
        {
            m_acquireData = false;
            m_nDataCount = 0;
            OnAcquireDataChanged();
        }

        SetModifiedFlag();
        InvalidateControl();
        COleControl::OnTimer(nIDEvent);
    }
}



Finally, you override the OnDraw function to provide visual feedback of your control’s state. The background of the control is green, yellow, or red, indicating the thermal state of the control. When the control reaches the red state, the data collection is shut down. The control displays two labels: Power and Antenna. A green or red rectangle follows the Power label. Green indicates the control is collecting data, and red indicates data collection is off. Blue rectangles follow the Antenna label. Three blue rectangles indicate that antenna three is in use, two blue rectangles indicate antenna two is in use, and so on.

void CMFCActiveXCtrl::OnDraw(
            CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
    // the background of the control represents the thermal indication
    switch (m_nThermal)
    {
    case 1:
        pdc->FillSolidRect((LPCRECT)rcBounds, COLORREF(0x000000ff));
        break;
    case 2:
        pdc->FillSolidRect((LPCRECT)rcBounds, COLORREF(0x0000ffff));
        break;
    case 3:
        pdc->FillSolidRect((LPCRECT)rcBounds, COLORREF(0x0000ff00));
        break;
    }

    // provide a white area for the indicators
    CRect rcEmpty(rcBounds);
    rcEmpty.top = 9;
    rcEmpty.bottom = 25;
    rcEmpty.left = 9;
    rcEmpty.right = 92;
    pdc->FillSolidRect((LPCRECT)rcEmpty,COLORREF(0x000ffffff));
    rcEmpty.top = 29;
    rcEmpty.bottom = 45;
    rcEmpty.left = 9;
    rcEmpty.right = 136;
    pdc->FillSolidRect((LPCRECT)rcEmpty,COLORREF(0x000ffffff));

    // indicator labels
    pdc->SelectObject(::GetStockObject(ANSI_VAR_FONT));
    pdc->TextOut(rcBounds.left+10, rcBounds.top+10,“Power”, 5);
    pdc->TextOut(rcBounds.left+10, rcBounds.top+30,“Antenna”, 7);

    // draw power indicator
    CRect rcPower(rcBounds);
    	rcPower.top += 12;
    rcPower.bottom = rcPower.top + 10;
    rcPower.left = 70;
    rcPower.right = rcPower.left + 10;
    if (m_acquireData)
        pdc->FillSolidRect((LPCRECT)rcPower,COLORREF(0x0000ff00));
    else
        pdc->FillSolidRect((LPCRECT)rcPower,COLORREF(0x000000ff));

    // draw antenna indicator
    CRect rcTemp(rcBounds);
    rcTemp.top += 32;
    rcTemp.bottom = rcTemp.top+10;
    rcTemp.left +=70;
    for (int i=0; i<m_nAntenna; ++i)
    {
        rcTemp.right = rcTemp.left + 10;
        pdc->FillSolidRect((LPCRECT)rcTemp,COLORREF(0x00ee0000));
        rcTemp.left += 25;
    }
}

The constructor initializes the default values for various data members of the control.

CMFCActiveXCtrl::CMFCActiveXCtrl()
{
    InitializeIIDs(&IID_DMFCActiveX, &IID_DMFCActiveXEvents);

    // Initialize your control’s instance data here.
    m_nDataRate = 100;
    m_nAntenna = 1;
    m_nDataCount = 0;
    m_bufferSize = 5;
    m_nThermal = 3;
}

You can exercise the control using the ActiveX Control Test Container. This utility is available from the Tools menu. Insert the control using the Insert New Control option from the Edit menu (see Figure 14.17).


Figure 14.17  Inserting the MFCActiveX control.

Locate the MFCActiveX Control and click OK. The control will appear in the application’s view window (see Figure 14.18).


Figure 14.18  The MFCActiveX control inserted in the Test Container application.

If the control is not active, select it and select the Invoke Methods dialog from the Control menu. Changing the antenna causes the corresponding number of blue indicators to appear next to the antenna label. Select the SetAntenna method, and enter a number between one and three in the Parameter Value field. Press Set Value and Invoke (see Figure 14.19).


Figure 14.19  Invoking the SetAntenna method.

You will see the control update itself (see Figure 14.20).

To start the radar-mapping functions, select AcquireData (PropPut). Set the Parameter Value to one or any positive number. Click Invoke, and the bottom window of the test container application will begin reporting events that the control fires (see Figure 14.21).


Figure 14.20  An updated MFCActiveX control view.


Figure 14.21  The MFCActiveX control is firing events.

A Quick ATL Port

Now you will build the same control using ATL. The steps you take are similar to those in constructing an MFC control. ATL supports an application builder wizard and offers helper dialogs for defining methods, properties, and events. This project’s name is ATLActiveX (see Figure 14.22).

Accept the default wizard values, allow AppWizard to generate the code, and switch the workspace window to the Class View pane. Clicking the right mouse button on ATLActiveX classes displays a context menu with the New ATL Object option (see Figure 14.23).


Figure 14.22  Creating the ATLActiveX project.


Figure 14.23  Creating a new ATL object from the context menu.

The ATL Object Wizard offers a category list on the left and an object list on the right. Select the Controls category and the Full Control object. The name for the class is ATLActiveXCtl. Switching to the Attributes tab, you select Support Connection Points (see Figure 14.24). This allows you to define control events in an outgoing interface. You can take the defaults for all other values and select OK to generate code.

The wizard creates a new class, CATLActiveXCtl, and a new interface, _IATLActiveXCtlEvents. You add the method and properties in a manner that is similar to the procedures you follow when creating an MFC control. Right-clicking the IATLActiveXCtl interface entry in the Class View pane of the project window displays a context menu, allowing you to add methods and properties. Follow the steps for adding the methods and properties to the MFCActiveX project.


Figure 14.24  The Attributes tab in the ATL Object Wizard.



Now it is time to add events to the control. Identifying events in an ATL control is where things differ significantly from the steps you follow for an MFC control. A quick check of the context menu reveals that there is no Add Event menu. Instead you add events using the Add Method menu on an outgoing interface, in this case _IATLActiveXCtlEvents. Add two events with the same signatures as in the MFCActiveX project. The resulting .IDL file will appear as follows:

// ATLActiveX.idl : IDL source for ATLActiveX.dll
//

// This file will be processed by the MIDL tool to
// produce the type library (ATLActiveX.tlb) and marshalling code.

import “oaidl.idl”;
import “ocidl.idl”;
#include “olectl.h”


    [
        object,
        uuid(D33E5F63-CC5B-11D2-8FBA-00105A5D8D6C),
        dual,
        helpstring(“IATLActiveXCtl Interface”),
        pointer_default(unique)
    ]
    interface IATLActiveXCtl : IDispatch
    {
        [propget, id(1), helpstring(“property AcquireData”)]
         HRESULT AcquireData([out, retval] BOOL *pVal);
        [propput, id(1), helpstring(“property AcquireData”)]
         HRESULT AcquireData([in] BOOL newVal);
        [propget, id(2), helpstring(“property BufferSize”)]
         HRESULT BufferSize([out, retval] short *pVal);
        [propput, id(2), helpstring(“property BufferSize”)]
         HRESULT BufferSize([in] short newVal);
        [id(3), helpstring(“method SetAntenna”)]
         HRESULT SetAntenna(short Antenna);
    };

[
    uuid(D33E5F57-CC5B-11D2-8FBA-00105A5D8D6C),
    version(1.0),
    helpstring(“ATLActiveX 1.0 Type Library”)
]
library ATLACTIVEXLib
{
    importlib(“stdole32.tlb”);
    importlib(“stdole2.tlb”);

    [
        uuid(D33E5F64-CC5B-11D2-8FBA-00105A5D8D6C),
        helpstring(“_IATLActiveXCtlEvents Interface”)
    ]
    dispinterface _IATLActiveXCtlEvents
    {
        properties:
        methods:
        [id(1), helpstring(“method DataStream”)]
         HRESULT DataStream(BSTR Control);
        [id(2), helpstring(“method ThermalValue”)]
         HRESULT ThermalValue(short Indicator);
    };

    [
        uuid(D33E5F55-CC5B-11D2-8FBA-00105A5D8D6C),
        helpstring(“ATLActiveXCtl Class”)
    ]
    coclass ATLActiveXCtl
    {
        [default] interface IATLActiveXCtl;
        [default, source] dispinterface _IATLActiveXCtlEvents;
    };
};

Build the project. This ensures that the .IDL is compiled, which is necessary before proceeding.

The second step is to implement the connection point, which results in the creation of a proxy class. Right-click on the CATLActiveXCtl class and select the Implement Connection Point menu option. This displays the dialog shown in Figure 14.25.


Figure 14.25  The Implement Connection Point dialog.

The dialog contains an entry for the _IATLActiveXCtlEvents interface. Select the interface and click OK. The wizard will create a new class, CProxy_IATLActiveXCtlEvents, deriving from the IConnectionPointImpl template.

template <class T>
class CProxy_IATLActiveXCtlEvents : public IConnectionPointImpl<T, \
&DIID__IATLActiveXCtlEvents, CComDynamicUnkArray>
{
    //Warning this class may be recreated by the wizard.
public:
    HRESULT Fire_DataStream(BSTR Control)
    {
        CComVariant varResult;
        T* pT = static_cast<T*>(this);
        int nConnectionIndex;
        CComVariant* pvars = new CComVariant[1];
        int nConnections = m_vec.GetSize();

        for (nConnectionIndex = 0; nConnectionIndex < nConnections; \
             nConnectionIndex++)
        {
            pT->Lock();
            CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
            pT->Unlock();
            IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
            if (pDispatch != NULL)
            {
                VariantClear(&varResult);
                pvars[0] = Control;
                DISPPARAMS disp = { pvars, NULL, 1, 0 };
                pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, \
                       DISPATCH_METHOD, &disp, &varResult, NULL, NULL);
            }
        }
        delete[] pvars;
        return varResult.scode;
    }
    HRESULT Fire_ThermalValue(SHORT Indicator)
    {
        CComVariant varResult;
        T* pT = static_cast<T*>(this);
        int nConnectionIndex;
        CComVariant* pvars = new CComVariant[1];
        int nConnections = m_vec.GetSize();

        for (nConnectionIndex = 0; nConnectionIndex < nConnections; \
             nConnectionIndex++)
        {
            pT->Lock();
            CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
            pT->Unlock();
            IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
            if (pDispatch != NULL)
            {
                VariantClear(&varResult);
                pvars[0] = Indicator;
                DISPPARAMS disp = { pvars, NULL, 1, 0 };
                pDispatch->Invoke(0x2, IID_NULL, LOCALE_USER_DEFAULT,
                     DISPATCH_METHOD, &disp, &varResult, NULL, NULL);
            }
        }
        delete[] pvars;
        return varResult.scode;
    }
};

The final steps involve cutting and pasting the application code from the MFC control to the appropriate places in the ATL control. You add the timer callback function by selecting the Add Windows Message Handler option from the CATLActiveXCtl class object (see Figure 14.26).


Figure 14.26  Adding an event handler for the timer message.

Select the WM_TIMER message and proceed to edit the handler. There are a few porting issues in moving the code from MFC to ATL. Here is a summary of these issues:

  Replace InvalidateControl() calls with FireViewChange();.
  The device context is not a CDC object. Replace pdc-> references with GDI-level calls that accept di.hdcDraw as the first parameter.
  Replace FillSolidRect with FillRect, and create your own solid brushes with CreateSolidBrush. Delete the brush with a DeleteObject call.
  Replace FireThermalValue with Fire_ThermalValue.

You can exercise the control using the ActiveX Control Test Container.

Summary

In this chapter, you were made aware of the different “faces” a control has to offer: runtime and design time. You have seen how OLE control technology leverages automation to expose methods and properties. Finally, you have experimented with control creation using both MFC and ATL.